/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	WindowingSystem.cpp
*
*	Comments	-	See WindowingSystem.h
*
**************************************************************************************/
#include "../Include/WindowingSystem.h"
#include "../Include/String.h"
#include "../Include/EventInputs.h"
#include "../Include/Engine.h"
#include "../Include/EventWindowing.h"
#include "../Include/InputSystem.h"

namespace Pulse
{
	WindowingSystem::WindowingSystem( void )
	{
	}

	WindowingSystem::~WindowingSystem( void )
	{
	}

	EErrorCode::Type WindowingSystem::Initialize( void )
	{
		// Register input events
		EventSystem *pEventSys = Engine::GetInstance()->GetEventSystem();
		pEventSys->AddListener( EEvent::INPUT_KEY_UP, this, &WindowingSystem::OnKeyUp );
		pEventSys->AddListener( EEvent::INPUT_KEY_DOWN, this, &WindowingSystem::OnKeyDown );
		pEventSys->AddListener( EEvent::INPUT_MOUSE_MOVE, this, &WindowingSystem::OnMouseMove );
		pEventSys->AddListener( EEvent::INPUT_MOUSE_BUTTONUP, this, &WindowingSystem::OnMouseButtonUp );
		pEventSys->AddListener( EEvent::INPUT_MOUSE_BUTTONDOWN, this, &WindowingSystem::OnMouseButtonDown );

		// Register to accept Gain/Lose focus events
		// NOTE: These should probably be in the WindowingSystem not the engine...
		Engine::GetInstance()->GetEventSystem()->AddListener( EEvent::WINDOW_GAIN_FOCUS, this, &WindowingSystem::OnWindowGainFocus );
		Engine::GetInstance()->GetEventSystem()->AddListener( EEvent::WINDOW_LOSE_FOCUS, this, &WindowingSystem::OnWindowLoseFocus );

		m_pActiveWindow = PSX_NULL;

		return EErrorCode::OKAY;
	}

	void WindowingSystem::Shutdown( void )
	{
		// Unregister input events
		EventSystem *pEventSys = Engine::GetInstance()->GetEventSystem();
		pEventSys->RemoveListener( EEvent::INPUT_KEY_UP, this, &WindowingSystem::OnKeyUp );
		pEventSys->RemoveListener( EEvent::INPUT_KEY_DOWN, this, &WindowingSystem::OnKeyDown );
		pEventSys->RemoveListener( EEvent::INPUT_MOUSE_MOVE, this, &WindowingSystem::OnMouseMove );
		pEventSys->RemoveListener( EEvent::INPUT_MOUSE_BUTTONUP, this, &WindowingSystem::OnMouseButtonUp );
		pEventSys->RemoveListener( EEvent::INPUT_MOUSE_BUTTONDOWN, this, &WindowingSystem::OnMouseButtonDown );

		Engine::GetInstance()->GetEventSystem()->RemoveListener( EEvent::WINDOW_GAIN_FOCUS, this, &WindowingSystem::OnWindowGainFocus );
		Engine::GetInstance()->GetEventSystem()->RemoveListener( EEvent::WINDOW_LOSE_FOCUS, this, &WindowingSystem::OnWindowLoseFocus );

		ClearWindowViews();
		ClearWindows();
	}

	EErrorCode::Type WindowingSystem::CreateWindow_( const CHAR *pName, Window **ppOut )
	{
		if ( ppOut == PSX_NULL )
		{
			PSX_PushError( "ppWindow must not be null." );
			return EErrorCode::ARGS;
		}

		Window *pWindow = FindWindow_( pName );

		if ( pWindow )
		{
			*ppOut = PSX_NULL;
			return EErrorCode::ALREADY_EXISTS;
		}

		Window *pNewWindow = PSX_NULL;
		BOOL bMainWindow = GetNumWindows() ? FALSE : TRUE;
		
		EErrorCode::Type retCode = Window::Create( pName, &pNewWindow, bMainWindow );

		if ( retCode != EErrorCode::OKAY )
		{
			return retCode;
		}

		m_windows.PushBack( pNewWindow );

		*ppOut = pNewWindow;

		return EErrorCode::OKAY;
	}

	void WindowingSystem::RemoveWindow( const CHAR *pWindowName )
	{
		String windowToFind( pWindowName );

		WindowList::Iterator iter = m_windows.IteratorBegin();
		WindowList::Iterator iterEnd = m_windows.IteratorEnd();

		while ( iter != iterEnd )
		{
			if ( windowToFind == (*iter)->GetName() )
			{
				DeleteWindow( *iter );
				m_windows.Remove( iter );
				break;
			}
		}
	}

	void WindowingSystem::RemoveWindow( const UPOS_T index )
	{
		DeleteWindow( m_windows[index] );
		m_windows.Remove( index );
	}

	Window * WindowingSystem::FindWindow_( const CHAR *pWindowName )
	{
		String windowToFind( pWindowName );

		for ( SIZE_T i = 0; i < m_windows.GetSize(); ++i )
		{
			if ( windowToFind == m_windows[i]->GetName() )
				return m_windows[i];
		}

		return PSX_NULL;
	}

	EErrorCode::Type WindowingSystem::CreateWindowView( const CHAR *pViewName, WindowView **ppOut )
	{
		if ( ppOut == PSX_NULL)
		{
			PSX_PushError( "ppOut must not be null." );
			return EErrorCode::ARGS;
		}

		WindowView *pView = FindWindowView( pViewName );

		if ( pView )
		{
			*ppOut = PSX_NULL;
			return EErrorCode::ALREADY_EXISTS;
		}

		WindowView *pNewView = PSX_NULL;

		EErrorCode::Type retType = WindowView::Create( pViewName, &pNewView );
	
		if ( retType != EErrorCode::OKAY )
		{
			return retType;
		}

		m_views.PushBack( pNewView );

		*ppOut = pNewView;

		return EErrorCode::OKAY;
	}

	WindowView * WindowingSystem::FindWindowView( const CHAR *pViewName )
	{
		if ( pViewName == PSX_NULL )
		{
			return PSX_NULL;
		}

		String viewToFind( pViewName );

		for ( SIZE_T i = 0; i  < m_views.GetSize(); ++i )
		{
			if ( viewToFind == m_views[i]->GetName() )
				return m_views[i];
		}

		return PSX_NULL;
	}

	EErrorCode::Type WindowingSystem::RemoveWindowView( const CHAR *pWindowViewName )
	{
		if ( pWindowViewName == PSX_NULL )
		{
			return EErrorCode::INVALID_PARAMETER;
		}

		String nameToFind( pWindowViewName );

		for ( SIZE_T i = 0; i < m_views.GetSize(); ++i )
		{
			if ( nameToFind == m_views[i]->GetName() )
			{
				DeleteWindowView( m_views[i] );
				m_views.Remove( i );

				return EErrorCode::OKAY;
			}
		}

		return EErrorCode::NOT_FOUND;
	}

	void WindowingSystem::Update( const FLOAT dt )
	{
		m_eventDispatcher.ProcessEvents();

		// NOTE: Update then render or update all then render all in batch?


		m_eventDispatcher.ProcessEvents();

		for ( SIZE_T i = 0; i < m_windows.GetSize(); ++i )
		{
			Window *pWindow = m_windows[i];

			if ( pWindow->IsSafeToClose() == FALSE )
			{
				pWindow->Update( dt );
			}

			if ( pWindow->IsSafeToClose() )
			{
				if ( pWindow->IsMainWindow() )
				{
					ClearWindows();
				}
				else
				{
					RemoveWindow( i );
				}
			}
		}
	}

	void WindowingSystem::Render( void )
	{
		for ( SIZE_T i = 0; i < m_windows.GetSize(); ++i )
		{
			Window *pWindow = m_windows[i];

			//if ( pWindow->IsSafeToClose() == FALSE )
			//{
				pWindow->Render();
			//}
		}
	}

	void WindowingSystem::ClearWindowViews( void )
	{
		for ( SIZE_T i = 0; i < m_views.GetSize(); ++i )
		{
			DeleteWindowView( m_views[i] );
		}

		m_views.Clear();
	}

	void WindowingSystem::ClearWindows( void )
	{
		for ( POS_T i = m_windows.GetSize() - 1; i >= 0; --i )
		{
			DeleteWindow( m_windows[i] );
		}

		m_windows.Clear();
	}

	void WindowingSystem::DeleteWindow( Window *pWindow )
	{
		if ( pWindow == m_pActiveWindow )
		{
			m_pActiveWindow = PSX_NULL;
		}

		Window::Delete( pWindow );
	}

	void WindowingSystem::DeleteWindowView( WindowView *pView )
	{
		// Inform the Window owner that this view is to be deleted
		Window *pOwner = pView->GetOwner();

		if ( pOwner )
		{
			EErrorCode::Type retCode = pOwner->FromWindowSysRemoveView( pView );
		
			if ( retCode != EErrorCode::OKAY )
			{
				PSX_PushError( "Failed to remove WindowView in Window owner's list." );
			}
		}

		WindowView::Delete( pView );
	}

	void WindowingSystem::OnKeyUp( IEvent *pEvent )
	{
		//EventKeyUp *pDerivedEvent = (EventKeyUp*)pEvent;
		m_eventDispatcher.PostEventUnmanaged( pEvent );
	}

	void WindowingSystem::OnKeyDown( IEvent *pEvent )
	{
		//EventKeyDown *pDerivedEvent = (EventKeyDown*)pEvent;
		m_eventDispatcher.PostEventUnmanaged( pEvent );
	}

	void WindowingSystem::OnMouseMove( IEvent *pEvent )
	{
		//EventMouseMove *pDerivedEvent = (EventMouseMove*)pEvent;
		m_eventDispatcher.PostEventUnmanaged( pEvent );
	}

	void WindowingSystem::OnMouseButtonUp( IEvent *pEvent )
	{
		//EventMouseButtonUp *pDerivedEvent = (EventMouseButtonUp*)pEvent;
		m_eventDispatcher.PostEventUnmanaged( pEvent );
	}

	void WindowingSystem::OnMouseButtonDown( IEvent *pEvent )
	{
		//EventMouseButtonDown *pDerivedEvent = (EventMouseButtonDown*)pEvent;
		m_eventDispatcher.PostEventUnmanaged( pEvent );
	}

	void WindowingSystem::OnWindowGainFocus( IEvent *pEvent )
	{
		EventWindowGainFocus *pDerivedEvent = (EventWindowGainFocus*)pEvent;

		m_pActiveWindow = pDerivedEvent->GetWindow();
	}

	void WindowingSystem::OnWindowLoseFocus( IEvent *pEvent )
	{
		m_pActiveWindow = PSX_NULL;
	}
}